
/*
 XMail OSAX -- Copyright (C) 2004, 2005, 2006, 2007 Jean-Baptiste LE STANG osax@lestang.org
 
	
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 */

#include "smtp.h"
#include "smtp-string.h"
#include "sslsocket.h"
#include "dlist.h"

#include "auth-anonymous.h"
#include "auth-cram-md5.h"
#include "auth-cram-sha1.h"
#include "auth-login.h"
#include "auth-plain.h"
#include "auth-pop3.h"

/**
* smtp_write() sends data to the SMTP server. All lines
 * ending in <LF> not preceeded by <CR> will be converted 
 * to <CR><LF> as <EOL>.
 *
 * @param srv   The smtp_srv_t object.
 * @param data  The data that should be send.
 * @param siz   The size of data in bytes.
 *
 * @returns 0 on success and -1 on any error.
 */
int smtp_write (smtp_srv_t * srv, const char * data, unsigned int siz){
    unsigned int idx, buf_idx, bytes;
    int ret = 0;
    
    char buf[4096]; /* send 4k blocks of data, any better idea ? */
    
    buf_idx = 0;
    for (idx = 0; idx < siz; idx++)
    {
        /* unix2dos */
        if (data[idx] == '\n' && (idx == 0 || data[idx-1] != '\r')){
            buf[buf_idx++] = '\r';
            buf[buf_idx++] = '\n';
        } else if ((data[idx] == '\r' && idx == (siz-1)) || (data[idx] == '\r' && idx <  (siz-1)  && data[idx+1] != '\n')) {
            buf[buf_idx++] = '\r';
            buf[buf_idx++] = '\n';
        } else  {
            buf[buf_idx++] = data[idx];
        }
		
        if (buf_idx >= (4094) || idx == (siz-1)){
            buf[buf_idx] = '\0';
            buf_idx      = 0;
			
            bytes = 0;
            while (bytes < strlen (buf)){
                if (srv->init != NULL){
                    if ((ret = srv->write (srv, buf+bytes, strlen (buf)-bytes)) == -1){
                        return (-1); /* error msg is already set */
                    }
                } else {
                    if ((ret = ssl_write (&srv->sck, buf+bytes, strlen (buf)-bytes)) == -1) {
                        return (-1); /* error msg is already set */
                    }
                }
                bytes += ret;
            }
        }
    };
	
    return (0);
}

/**
* smtp_read_() reads one line from the SMTP server,
 * which must be already connected. Any trailing '\r' 
 * will be removed if present. The resulting string is 
 * malloc()ed and must be freed using free().
 *
 * @param srv  The SMTP server object.
 *
 * @returns A pointer to the resulting string or NULL
 * if smtp_read_() failed. The string will be NULL
 * terminated.
 */
char * smtp_read_ (smtp_srv_t * srv){
    int idx    = 0;
    int bufsiz = 4096;
    char *buf  = (char*)malloc(sizeof(char)*bufsiz);
    
    do {
        if (srv->init != NULL) {
            if (srv->read (srv, &buf[idx++], 1) == -1) {
                free(buf);
                return (NULL); /* error msg is already set */
            }
        } else {
            if (ssl_read (&srv->sck, &buf[idx++], 1) == -1) {
                free (buf);
                return (NULL); /* error msg is already set */
            }
			
        } if (idx == bufsiz) {
            bufsiz += 4096;
            buf = (char*)realloc(buf, bufsiz);
        }
    } while (buf[idx-1] != '\n');
	
    /* get rid of the trailing \r and ensure a terminating NULL
		* character */
    if (buf[idx-2] == '\r'){
        strcpy (&buf[idx-2], "\n");
    } else {
        strcpy (&buf[idx-1], "\n");
    }
    return (buf);
}

/**
* smtp_eval_response() evaluates the response code given by
 * the SMTP server msg. If the status code indicates a fatal
 * error -1 will be returned, if the status code is non-fatal
 * the response string will be log()ged with a verbosity level 
 * of LOG_LVL_ERROR. If some unknown status code is returned by
 * the server smtp_eval_response() will also return -1.
 * If is_221_fatal is 1, smtp_eval_response() will also return
 * -1 if it receives the status code 221.
 *
 * @param srv           The SMTP server object.
 * @param rsp           The response string received from the 
 *                      SMTP server.
 * @param is_221_fatal  Must be 1 if the SMTP reply code 221 is
 *                      considered to be fatal, otherwise 0.
 *
 * @returns 0 on success and -1 if the status code in the response
 * from the SMTP server indicates an error.
 */
static int smtp_eval_response (smtp_srv_t * srv,  char * rsp, int is_221_fatal){
    int idx = 0;
    
    char *fatal[] = { 
		"421", "432", "450", "451", 
		"452", "454", "500", "501", 
		"502", "503", "504", "530", 
		"534", "535", "538", "550", 
		"552", "553", "554", NULL
	};
	
    char *notify[] = {
		"211", "214", "251", "252", 
		"551", NULL
	};
	
    char *ok[] = {
		"220", "221", "235", "250", 
		"334", "354", NULL
	};
	
    if (strlen (rsp) >= 3) {
        /* check for 221 if this is expected to be an error */
        if (is_221_fatal && strncmp (rsp, "221", 3) == 0) {
			smtp_set_error(srv,rsp,221);
			return (-1);
        }
		
        while (fatal[idx] != NULL) {
            if (strncmp (rsp, fatal[idx++], 3) == 0) {
				int errnum; sscanf(fatal[idx-1],"%d",&errnum);
				smtp_set_error(srv,rsp,errnum);
                return (-1);
            }
        }
		
        idx = 0;
        while (notify[idx] != NULL){
            if (strncmp (rsp, notify[idx++], 3) == 0){
                return (0);
            }
        }
		
        idx = 0;
        while (ok[idx] != NULL){
            if (strncmp (rsp, ok[idx++], 3) == 0){
                return (0);
            }
        }
    }
	
	smtp_set_error(srv,rsp,-1);
    return (-1);
}

/**
* smtp_read_response_() reads the response from the SMTP server.
 * The resulting string is malloc()ed and must be freed using 
 * free().
 *
 * @param srv           The SMTP server object.
 * @param is_221_fatal  Must be 1 if the SMTP reply code 221 is
 *                      considered to be fatal, otherwise 0.
 *
 *
 * @returns A pointer to the resulting string or NULL
 * if smtp_read_response_() failed or a protocol error
 * occured. The string will be NULL terminated.
 */
char * smtp_read_response_ (smtp_srv_t * srv, int is_221_fatal){
    char *response = NULL;
    char *line = NULL;
    
    if ((line = smtp_read_ (srv)) == NULL){
        return (NULL); /* error msg is already set */
    }
    
    if (smtp_eval_response (srv, line, is_221_fatal) == -1){
        free(line);
        return (NULL); /* error msg is already set */
    };
    
    response = strdup(line);
	
    while (strlen (line) > 3 && line[3]  == '-'){
        free(line);
		
        if ((line = smtp_read_ (srv)) == NULL){
            free(response);
            return (NULL); /* error msg is already set */
        }
		
        if (smtp_eval_response (srv, line, is_221_fatal) == -1){
            free(response); free(line);
            return (NULL); /* error msg is already set */
        };
        
        response = (char*)realloc(response, strlen(response) + strlen (line) + 1);
        strcat (response, line);
    }
    free(line);
	
    return (response);
}

/**
* smtp_check_auth_method() checks if the SASL mechanism is
 * supported by the server.
 *
 * @param srv   The SMTP server object.
 * @param rsp   The response from the SMTP server to the
 *              EHLO command.
 * @param sasl  The SASL mechanism that should be proved.
 *
 * @returns 0 if the SASL mechanism is supported and -1 if it
 * is not.
 */
static int smtp_check_auth_method (smtp_srv_t * srv, const char * rsp, sasl_type_t sasl){
    char *tok = NULL;
    char *SASL  = NULL;
    char *RSP   = strdup(rsp);
    char *p_ptr = RSP;
	char * format = "Authentication method %s is not supported by server.";
    char * errmsg = NULL;
    unsigned int i = 0;
    int found = 0;
    
    sasl_type_t list[] = {CRAMMD5,CRAMSHA1,LOGIN,PLAIN,ANONYMOUS,-1};
    char * slist[] = {"CRAM-MD5","CRAM-SHA1","LOGIN","PLAIN","ANONYMOUS",NULL};
    
    while ( list[i] != -1 && !found){
        if (list[i] == sasl){
            SASL = slist[i];
            found = 1;
        } else {
            i++;
        }
    }
    
    str_toupper (RSP, strlen (RSP));
    //str_toupper (SASL, strlen (SASL));
	
    found = 0;
    
    while (p_ptr != NULL && !found){
        tok = str_tok_ (p_ptr, &p_ptr, "\n");
        if (strstr (tok, "AUTH") != NULL){
            if (strstr (tok, SASL) != NULL){
                found = 1;
            }
        }
        free(tok);
    };
    
	if (!found){
        errmsg = (char *)malloc(sizeof(char)*(strlen(SASL)+strlen(format)-2));
        sprintf(errmsg,format,SASL);
        smtp_set_error(srv,errmsg,-1);
        free(errmsg);
    }
    free(RSP);
    return found;
}

/**
* smtp_best_auth_method() takes the response to the EHLO
 * command from the SMTP server and returns the securest
 * AUTH mechanism that we have in common. The string is
 * directly taken from the plugin list, so it should not
 * be modified or freed in any way.
 *
 * @param srv  The SMTP server object.
 * @param rsp  The response from the SMTP server to the
 *             EHLO command.
 *
 * @returns  The securest AUTH mechanism that we have in 
 * common or NULL if nothing suitable was found.
 */
sasl_type_t smtp_best_auth_method (smtp_srv_t * srv, const char *rsp){
    int idx = 0;
    sasl_type_t plgs[] = {CRAMMD5,CRAMSHA1,LOGIN,PLAIN,ANONYMOUS,-1};
	char * plg[] = {"CRAM-MD5","CRAM-SHA1","LOGIN","PLAIN","ANONYMOUS",NULL};
    while (plg[idx] != NULL){
        if (smtp_check_auth_method (srv, rsp, plgs[idx]) == 1){
            return (plgs[idx]);
        }		
        idx += 1;
    }
    return -1;
}

/**
* smtp_send_HELO() sends the HELO command.
 *                           
 * @param srv  The SMTP server object.
 *
 * @returns 0 on success and -1 if smtp_send_HELO() 
 * failed or a protocol error occured. 
 */
static int smtp_send_HELO (smtp_srv_t * srv){
    char *response = NULL;
    char *msg = NULL;
    char * format = "HELO %s\r\n";
	int size = strlen(srv->domain)+strlen(format)+1;
    msg = (char*)malloc(sizeof(char)*size);
	memset(msg,0,size);
    sprintf (msg, format, srv->domain);
	
    if (smtp_write (srv, msg, strlen (msg)) == -1){
        free(msg);
        return (-1); /* error msg is already set */
    }
    free(msg);
	
    if ((response = smtp_read_response_ (srv, 1)) == NULL){
        return (-1); /* error msg is already set */
    }
    free(response);
	
    return (0);
}

/**
* smtp_authenticate() calls the plugin that handles the 
 * srv->SASL mechanism. It is upposed to be called by
 * smtp_send_EHLO().
 *
 * @param srv  The smtp_srv_t object that should be used.
 *
 * @returns 0 on success and -1 on any error.
 */
static int smtp_authenticate (smtp_srv_t * srv)
{
	if (srv->sasl != -1){   
		if(srv->sasl == PLAIN){
			srv->init = auth_plain_init;
			srv->cleanup = auth_plain_cleanup;
			srv->authenticate = auth_plain_authenticate;
			srv->read = auth_plain_read;
			srv->write = auth_plain_write;
		} else if (srv->sasl == LOGIN) {
			srv->init = auth_login_init;
			srv->cleanup = auth_login_cleanup;
			srv->authenticate = auth_login_authenticate;
			srv->read = auth_login_read;
			srv->write = auth_login_write;
		} else if (srv->sasl == DIGESTMD5) {
			srv->init = auth_cram_md5_init;
			srv->cleanup = auth_cram_md5_cleanup;
			srv->authenticate = auth_cram_md5_authenticate;
			srv->read = auth_cram_md5_read;
			srv->write = auth_cram_md5_write;
		} else if (srv->sasl == CRAMMD5) {
			srv->init = auth_cram_md5_init;
			srv->cleanup = auth_cram_md5_cleanup;
			srv->authenticate = auth_cram_md5_authenticate;
			srv->read = auth_cram_md5_read;
			srv->write = auth_cram_md5_write;	
		} else if (srv->sasl == CRAMSHA1) {
			srv->init = auth_cram_sha1_init;
			srv->cleanup = auth_cram_sha1_cleanup;
			srv->authenticate = auth_cram_sha1_authenticate;
			srv->read = auth_cram_sha1_read;
			srv->write = auth_cram_sha1_write;
		} else if (srv->sasl == ANONYMOUS) {
			srv->init = auth_anonymous_init;
			srv->cleanup = auth_anonymous_cleanup;
			srv->authenticate = auth_anonymous_authenticate;
			srv->read = auth_anonymous_read;
			srv->write = auth_anonymous_write;
		} else if (srv->sasl == POP3) {
			srv->init = auth_pop3_init;
			srv->cleanup = auth_pop3_cleanup;
			srv->authenticate = auth_pop3_authenticate;
			srv->read = auth_pop3_read;
			srv->write = auth_pop3_write;
		} else if (srv->sasl == APOP) {
			srv->init = auth_pop3_init;
			srv->cleanup = auth_pop3_cleanup;
			srv->authenticate = auth_apop_authenticate;
			srv->read = auth_pop3_read;
			srv->write = auth_pop3_write;
		}
		return(srv->authenticate(srv));
	}
	return -1;
}

/**
* smtp_send_EHLO() sends the EHLO command.
 *                           
 * @param srv  The SMTP server object.
 *
 * @returns 0 on success and -1 if smtp_send_EHLO() 
 * failed or a protocol error occured. 
 */
static int smtp_send_EHLO (smtp_srv_t * srv){
    char *response = NULL;
    char *msg = NULL;
    char * format = "EHLO %s\r\n";
    int size = strlen(srv->domain) + strlen(format);
	msg = (char*)malloc(sizeof(char)*size);
    sprintf (msg, format, srv->domain);
	
    if (smtp_write (srv, msg, strlen (msg)) == -1){
        free(msg);
        return (-1); /* error msg is already set */
    }
    free(msg);
	
    if ((response = smtp_read_response_ (srv, 1)) == NULL){
        return (-1); /* error msg is already set */
    }
    
	if (srv->sasl != -1 && srv->sasl != POP3 && srv->sasl != APOP){
		/* determine the SASL mechanism that should be used and if it is supported */
		if (srv->sasl == AUTO){
			sasl_type_t auth = smtp_best_auth_method (srv, response);
			if (auth != -1){
				srv->sasl = auth;
			} else {
				return (-1);
			}
		} else {
			if (smtp_check_auth_method (srv, response, srv->sasl) == -1) {
				free(response);
				return (-1); /* error msg is already set */
			}
		}
		free(response);
	}
    /* start the authentication */
    if (smtp_authenticate (srv) == -1){
        return (-1); /* error msg is already set */
    }
	
    return (0);
}


static int smtp_send_finger_EHLO (smtp_srv_t * srv){
    char *response = NULL;
    char *msg = NULL;
    char * format = "EHLO %s\r\n";
    int res = 0;
    
    int size = strlen(srv->domain) + strlen(format);
	msg = (char*)malloc(sizeof(char)*size);
    sprintf (msg, format, srv->domain);
	
    if (smtp_write (srv, msg, strlen (msg)) == -1){
        free(msg);
        return (-1); /* error msg is already set */
    }
    free(msg);
	
    if ((response = smtp_read_response_ (srv, 1)) == NULL){
        return (-1); /* error msg is already set */
    }
    
    if (smtp_check_auth_method (srv, response, CRAMMD5) != 0) {
        res |= 0x00000001;
    }
    if(smtp_check_auth_method (srv, response, CRAMSHA1) != 0) {
       res |= 0x00000010;
    }
    if (smtp_check_auth_method (srv, response, ANONYMOUS) != 0) {
        res |= 0x00000100;
    }
    if (smtp_check_auth_method (srv, response, PLAIN) != 0) {
        res |= 0x00001000;
    }
    if (smtp_check_auth_method (srv, response, LOGIN) != 0) {
       res |= 0x00010000;
    }
    
    free(response);
    return (res);
}


/**
* smtp_send_MAILFROM() sends the MAIL FROM command.
 *                           
 * @param srv  The SMTP server object.
 * @param from   The address of the sender.
 *
 * @returns 0 on success and -1 if smtp_send_MAILFROM() 
 * failed or a protocol error occured. 
 */
static int smtp_send_MAILFROM (smtp_srv_t * srv, const char * from){
    char *response = NULL;
    char *msg = NULL;
    char * format = "MAIL FROM: <%s>\r\n";
	int size = strlen(format)+strlen(from);
	 
    msg = (char*)malloc(sizeof(char)*size);
	memset(msg,0,size);
    sprintf (msg, format, from);
	
    if (smtp_write (srv, msg, strlen (msg)) == -1){
        if (msg != NULL) free(msg);
        return (-1); /* error msg is already set */
    }
    free(msg);
	
    if ((response = smtp_read_response_ (srv, 1)) == NULL) {
        return (-1); /* error msg is already set */
    }
    free(response);
	
    return (0);
}

/**
* smtp_send_QUIT() sends the QUIT command.
 *                           
 * @param srv  The SMTP server object.
 *
 * @returns 0 on success and -1 if smtp_send_QUIT() 
 * failed or a protocol error occured. 
 */
static int smtp_send_QUIT (smtp_srv_t * srv)
{
    char *response = NULL;
	
    if (smtp_write (srv, "QUIT\r\n", 6) == -1){
        return (-1); /* error msg is already set */
    }
	
    if ((response = smtp_read_response_ (srv, 0)) == NULL){
        return (-1); /* error msg is already set */
    }
    free(response);
	
    return (0);
}

/**
* smtp_send_RCPTTO() sends the RCPTTO command.
 *                           
 * @param srv  The SMTP server object.
 *
 * @returns 0 on success and -1 if smtp_send_RCPTTO() 
 * failed or a protocol error occured. 
 */
static int smtp_send_RCPTTO (smtp_srv_t * srv, DList * rcpts,int startOffset,int endOffset)
{
    char *response = NULL;
    char *msg = NULL;
    int idx = 0, valid = 0;
	DListElmt * e = NULL;
	char * rcpt = NULL;
	int size = 0;
	
	char * format = "RCPT TO: <%s>\r\n";
	idx = startOffset;

    while (idx != endOffset ){
		rcpt = NULL;
		if (idx==startOffset){
			e = dlist_element(rcpts,idx);
			rcpt = e->donnee;
		} else {
			e = dlist_next(e);
			rcpt = e->donnee;
		}
		size = strlen(rcpt) + strlen(format);
        msg = (char*)malloc(sizeof(char)*size);
		memset(msg,0,size);
        sprintf (msg, format, rcpt);
        if (smtp_write (srv, msg, strlen (msg)) == -1){
            free(msg);
            return (-1); /* error msg is already set */
        }
        free(msg);
		
        if ((response = smtp_read_response_ (srv, 1)) == NULL){
            return (-1);
        } else {
            valid += 1;
            free(response);
        }
		
        idx += 1;
    }
	
    return (0);
}

/**
* smtp_init() initializes the smtp_srv_t struct srv and must
 * be called once for each smtp_srv_t object before the other
 * functions can be used. When the smtp_srv_t object is no longer
 * needed it should be de-initialized with smtp_cleanup() to free
 * unneeded resources.
 *
 * @param srv      The smtp_srv_t object.
 * @param domain   The domain that should be used when sending the HELO cmd.
 * @param from     The envelope from address.
 * @param server   The host of the SMTP server.
 * @param port     The port the SMTP server is listening on.
 * @param timeout  Socket timout value in seconds.
 */
void smtp_init (smtp_srv_t * srv,  char * domain,  char * from,  char * server, unsigned int port, unsigned int timeout){
    srv->domain =  domain;
    srv->from   =  from;
    srv->server =  server;
    
    srv->timeout = timeout;
    srv->port    = port;
   	
    srv->init = NULL;
	srv->authenticate = NULL;
	srv->read = NULL;
	srv->write = NULL;
	srv->cleanup = NULL;
	
	srv->username = NULL;
    srv->password = NULL;
    srv->realm    = NULL;
    srv->sasl     = -1;
	srv->ssl_type = NO_SSL;
	srv->error = (char *)malloc(sizeof(char)*256);
	memset(srv->error,0,256);
	srv->errornum = 0;
    ssl_init (&srv->sck);
	#define MAILLOG ""
	if (srv->loglevel == 1){
		srv->output = fopen(MAILLOG,"ar+");
	} else {
		srv->output = NULL;
	}
}

/**
* smtp_cleanup() releases unneeded resources and must be called when
 * the smtp_srv_t object is no longer needed. srv must be initialized
 * by smtp_init() before, otherwise random errors may occur.
 *
 * @param srv   The smtp_srv_t object.
 */
void smtp_cleanup (smtp_srv_t * srv){
	/* Nothing to be done */
	free(srv->error);
	if (srv->loglevel == 1&& srv->output != NULL){
		fclose(srv->output);
	}
    ssl_cleanup (&srv->sck);
}


/**
* smtp_ssl_opts() sets some SSL specific options.
 *
 * @param srv      The smtp_srv_t object that should be used.
 * @param ssl_type The ssl protocol flavor that should be used.
 * @param opts     The SSL verification level.
 * @param ca_file  A file that contains PEM certificates. Must
 *                 be "" if opts == VERIFY_NONE.
 *                 The file  can  contain several CA certificates 
 *                 identified by
 *                 
 *                 -----BEGIN CERTIFICATE-----
 *                 
 *                 ... [CA certificate in base64 encoding] ...
 *                 
 *                 -----END CERTIFICATE-----
 *                 
 *                 sequences.  Before,  between, and after the 
 *                 certificates text is allowed which
 *                 can be used e.g. for descriptions of the 
 *                 certificates.
 *                 
 *                 Take a look in the openssl documentation to 
 *                 get more infos on that topic.
 * @param ca_dir   A directory that contains PEM certificates. Must
 *                 be "" if opts == VERIFY_NONE.
 *                 The files each contain one CA certificate.  The files 
 *                 are looked up by the CA subject name hash value, which 
 *                 must hence be available.  If more than one CA certificate 
 *                 with the same name hash value exist, the extension must be 
 *                 different (e.g. 9d66eef0.0, 9d66eef0.1 etc).  The search 
 *                 is performed in the ordering of the extension number, 
 *                 regardless of other properties of the certificates.  
 *                 Use the c_rehash utility to create the necessary links.  
 *                 
 *                 Take a look in the openssl documentation to 
 *                 get more infos on that topic.
 */
void smtp_ssl_opts (smtp_srv_t * srv, ssl_type_t ssl_type, ssl_opts_t opts,  char * ca_file,  char * ca_dir){
    srv->ssl_type = ssl_type;
    ssl_set_opts (&srv->sck, opts, ca_file, ca_dir);
}


/**
* ssl_negotiate() starts the SSL handshake.
 *
 * @param srv      The smtp_srv_t object that should be used.
 *
 * @returns -1 on an error, otherwise 0.
 */
static int smtp_ssl_handshake (smtp_srv_t * srv)
{
    char *rsp = NULL;
	
    if (srv->ssl_type != NO_SSL){
        if (srv->ssl_type == STARTTLS){
		
			if (smtp_send_HELO (srv) == -1){
				return (-1); /* error msg is already set */
			}
		
            if ((rsp = smtp_read_response_ (srv, 1)) == NULL){
                return (-1); /* error msg is already set */
            }
            free(rsp);
            
            if (smtp_write (srv, "STARTTLS\r\n", 10) == -1){
                return (-1); /* error msg is already set */
            }
			
            if ((rsp = smtp_read_response_ (srv, 1)) == NULL){
                return (-1); /* error msg is already set */
            }
            free(rsp);
        }
        
        if (ssl_negotiate (&srv->sck) == -1){
            return (-1); /* error msg is already set */
        }
    }
	
    return (0);
}


int smtp_finger_connect(smtp_srv_t * srv){
    char * response = NULL;
    int res = 0;
    srv->sck.sck.errnum = &(srv->errornum);
    srv->sck.errnum = &(srv->errornum);
    
    srv->sck.sck.error = &(srv->error);
    srv->sck.error = &(srv->error);
    
    if (ssl_connect (&srv->sck, srv->server, srv->port, srv->timeout) == -1){
        return (-1); /* error msg is already set */
    }
    
    if ((response = smtp_read_response_ (srv, 1)) == NULL){
        return (-1);
    }
    free(response);
    
    if ((res = smtp_send_finger_EHLO (srv)) == -1){
        return (-1); /* error msg is already set */
    }
    return (res);
}

/**
* smtp_connect() connects to the smtp_srv_t object.
 *
 * @param srv The SMTP server object.
 * 
 * @returns 0 on success and -1 on failure.
 */
int smtp_connect (smtp_srv_t * srv){
	char * response = NULL;
	
	/* socket error output set in the stmp structure :) */
	
	
	srv->sck.sck.errnum = &(srv->errornum);
	srv->sck.errnum = &(srv->errornum);
	
	srv->sck.sck.error = &(srv->error);
	srv->sck.error = &(srv->error);
	
    if (ssl_connect (&srv->sck, srv->server, srv->port, srv->timeout) == -1){
        return (-1); /* error msg is already set */
    }
	
    if (srv->ssl_type == STARTTLS){
        if (smtp_ssl_handshake (srv) == -1){
            return (-1); /* error msg is already set */
        }
    }
    if (srv->ssl_type == NO_SSL){
		if ((response = smtp_read_response_ (srv, 1)) == NULL){
            return (-1);
        }
        free(response);
    }
	
    if (srv->sasl != -1){
        if (smtp_send_EHLO (srv) == -1){
            return (-1); /* error msg is already set */
        }
    } else {
        if (smtp_send_HELO (srv) == -1){
            return (-1); /* error msg is already set */
        }
    }
	
    return (0);
}

/**
* smtp_close() closes the connection to the SMTP server.
 *
 * @param srv  The smtp_srv_t object that should be closed.
 *
 * @returns 0 on success and -1 on any error.
 */
int smtp_close (smtp_srv_t * srv){
    int ret = 0;
	
    /* if the QUIT command fails for some reason we
	* do not care and proceed closing the socket. */
    if (smtp_send_QUIT (srv) == -1){
        ret = -1;
    }
	
    if (ssl_close (&srv->sck) == -1){
        ret = -1;
    }
	
    return (ret);
}

/**
* smtp_send_msg() sends msg to all repipients in
 * the array rcpts. The last element of rcpts must be 
 * NULL. The msg will be converted into dos format, 
 * conforming to rfc0821.
 *
 * @param srv    The smtp_srv_t object that should be used.
 * @param msg    The msg that should be send.
 * @param rcpts  The list of recipients. The last element 
 *               this array must be NULL.
 *
 * @returns 0 on success and -1 on any error.
 */
int smtp_send_msg (smtp_srv_t * srv, const char * msg, DList * rcpts){
    return (smtp_send_big_msg (srv, msg, rcpts));
}

/**
* smtp_send_big_msg() can be used instead of smtp_send_msg() if
 * you send very big msgs. The memory allocated by smtp_send_msg()
 * is up to 3 * strlen (message), which can be very much if you 
 * consider people sending files that are 50MB big or even more via 
 * email. Using smtp_send_big_msg() the memory allocated will be only
 * 3 * strlen (part_message). The last element of rcpts must be NULL. 
 * The msg will be converted into dos format, conforming to rfc0821.
 *
 * Example:
 *
 *  char *msg_1 = "To: bouncer@test.smtp.org\n"
 *                "Subject: test\n"
 *                "\n"
 *                "part 1\n";
 *
 *  char *msg_2 = "\npart 2\n";
 *
 *  char *msg_3 = "\npart 3\n";
 *
 *  if (smtp_send_big_msg (&srv, msg_1, rcpts, 0) == -1)
 *  {
	 *      puts (err_str_error ());
	 *      smtp_cleanup (&srv);
	 *      exit (1);
	 *  }
 *
 *  if (smtp_send_big_msg (&srv, msg_3, rcpts, 0) == -1)
 *  {
	 *      puts (err_str_error ());
	 *      smtp_cleanup (&srv);
	 *      exit (1);
	 *  }
 *  
 *  if (smtp_send_big_msg (&srv, msg_3, rcpts, 1) == -1)
 *  {
	 *      puts (err_str_error ());
	 *      smtp_cleanup (&srv);
	 *      exit (1);
	 *  }
 *
 * @param srv    The smtp_srv_t object that should be used.
 * @param msg    The msg that should be send.
 * @param rcpts  The list of recipients. The last element 
 *               this array must be NULL.
 * @param last   If msg is the last segment of the msg, last must
 *               be 1, otherwise 0.
 *
 * @returns 0 on success and -1 on any error.
 */

#define SMTPMaxRecipients 100

int smtp_send_big_msg (smtp_srv_t * srv, const char * msg, DList * rcpts){
	
    int len = 0, idx = 0, buf_idx = 0;
    char buf[4096];
    char *rsp = NULL;
    int i = 0;
	int startOffset = 0;
	int endOffset = 0;
	
	for (i = 0 ; i < 1+(dlist_size(rcpts)-1) / SMTPMaxRecipients; i++){
		
		
		startOffset = (i)*SMTPMaxRecipients;
		endOffset = ((startOffset+(SMTPMaxRecipients-1))<dlist_size(rcpts))?(startOffset+(SMTPMaxRecipients-1)):dlist_size(rcpts);
		/* */
		//smtp_callback("sending to recipients %d trhu %d\n",startOffset+1,endOffset+1);
		

		if (smtp_send_MAILFROM (srv, srv->from) == -1){
			return (-1); /* error msg is already set */
		}
		
		if (smtp_send_RCPTTO (srv, rcpts, startOffset, endOffset) == -1){
			return (-1); /* error msg is already set */
		}
		
		/* the DATA command */
		if (smtp_write (srv, "DATA\r\n", 6) == -1){
			return (-1); /* error msg is already set */
		}
		
		if ((rsp = smtp_read_response_ (srv, 1)) == NULL){
			return (-1); /* error msg is already set */
		}
		free(rsp);
		
		
		/* send the msg */
		buf_idx = 0;
		len = strlen (msg);
		for (idx = 0; idx < len; idx++){
			/* rfc 2821 - 4.5.2 */
			if (msg[idx] == '.' && idx != 0 && (msg[idx-1] == '\n' || msg[idx-1] == '\r')){
				buf[buf_idx++] = '.';
				buf[buf_idx++] = '.';
			} else {
				buf[buf_idx++] = msg[idx];
			}
			
			if (buf_idx >= (4094) || idx == (len-1)){
				buf[buf_idx] = '\0';
				buf_idx      = 0;
				if (smtp_write (srv, buf, strlen (buf)) == -1){
					return (-1); /* error msg is already set */
				}
			}
		}
		
		if (smtp_write (srv, "\r\n.\r\n", 5) == -1){
			return (-1); /* error msg is already set */
		}
		
		if ((rsp = smtp_read_response_ (srv, 1)) == NULL){
			return (-1); /* error msg is already set */
		}
		free(rsp);
	}
	return (0);
}

/**
* smtp_set_credentials() sets the credentials that should be
 * used for authentication.
 *
 * @param srv       The smtp_srv_t object that should be used.
 * @param sasl      The SASL mechanism that should be used. 
 *                  If sasl == "AUTO" the securest mechanism that 
 *                  we have in common with the server will be 
 *                  determined automagically.
 * @param username  The username.
 * @param password  The password.
 * @param realm     The realm. 
 */
void smtp_set_credentials (smtp_srv_t * srv,  sasl_type_t sasl,  char * username,  char * password,  char * realm){	
    if (sasl != -1) srv->sasl = sasl;
    if (username != NULL) srv->username = (username);
    if (password != NULL) srv->password = (password);
    if (realm != NULL) srv->realm = (realm);
}

void smtp_set_error(smtp_srv_t * smtp,char * msg,int num){
	memset(smtp->error,0,256);
	strcpy(smtp->error,msg);
	smtp->errornum = num;
}

void smtp_callback(char * format,...){
	va_list args;
	va_start (args, format);
	vfprintf (stdout, format, args);
	va_end (args);
}
